#include "dialects/Builtin/Prototype.h"
#include "dialects/Builtin/Types.h"
#include "dialects/Math/Dialect.h"
#include "lcir/IR/ExprTemps.h"
#include "lcir/IR/Op.h"
#include "lcir/IR/Prototype.h"
namespace lcir::math {
static bool checkIntMathType(const std::vector<Prototype *> ops) {
  if (ops.size() != 2) return false;
  if (!ops[0]->getType()->isa<builtin::Int32Type>() || !ops[1]->getType()->isa<builtin::Int32Type>()) return false;
  return true;
}
static std::vector<Prototype *> deduceIntType(const std::vector<Prototype *> &operandProtos) {
  return {new builtin::Int32VarProto()};
}
static bool checkFloatMathType(const std::vector<Prototype *> ops) {
  if (ops.size() != 2) return false;
  if (!ops[0]->getType()->isa<builtin::FloatType>() || !ops[1]->getType()->isa<builtin::FloatType>()) return false;
  return true;
}
static std::vector<Prototype *> deduceFloatType(const std::vector<Prototype *> &operandProtos) {
  return {new builtin::FloatVarProto()};
}

static bool checkBoolMathType(const std::vector<Prototype *> ops) {
  if (ops.size() != 2) return false;
  if (!ops[0]->getType()->isa<builtin::BoolType>() || ops[1]->getType()->isa<builtin::BoolType>()) return false;
  return true;
}
static std::vector<Prototype *> deduceBoolType(const std::vector<Prototype *> &operandProtos) {
  return {new builtin::BoolVarProto()};
}

// -----------------------------------------------------------------------------
// 取反操作
class INeg : public OpCRTP<INeg, MathDialect> {
 public:
  INeg() : OpCRTP("INeg") {}
  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override {
    return operandVals.size() == 1 && operandVals[0]->getType()->isa<builtin::Int32Type>();
  }
  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    auto ret = new builtin::Int32VarProto();
    return {ret};
  }
};

// !操作
class ISeqz : public OpCRTP<ISeqz, MathDialect> {
 public:
  ISeqz() : OpCRTP("ISeqz") {}
  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override {
    return operandVals.size() == 1 && operandVals[0]->getType()->isa<builtin::Int32Type>();
  }
  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    auto ret = new builtin::Int32VarProto();
    return {ret};
  }
};
// 仿照上面Seqz和Fng实现浮点数版本
class FNeg : public OpCRTP<FNeg, MathDialect> {
 public:
  FNeg() : OpCRTP("FNeg") {}
  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override {
    return operandVals.size() == 1 && operandVals[0]->getType()->isa<builtin::FloatType>();
  }
  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    auto ret = new builtin::FloatVarProto();
    return {ret};
  }
};
class FSeqz : public OpCRTP<FSeqz, MathDialect> {
 public:
  FSeqz() : OpCRTP("FSeqz") {}
  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override {
    return operandVals.size() == 1 && operandVals[0]->getType()->isa<builtin::FloatType>();
  }
  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    auto ret = new builtin::FloatVarProto();
    return {ret};
  }
};
class IInz : public OpCRTP<IInz, MathDialect> {
 public:
  IInz() : OpCRTP("IInz") {}
  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override {
    return operandVals.size() == 1 && operandVals[0]->getType()->isa<builtin::FloatType>();
  }
  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    return {new builtin::BoolVarProto()};
  };
};
class FInz : public OpCRTP<FInz, MathDialect> {
 public:
  FInz() : OpCRTP("FInz") {}
  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override {
    return operandVals.size() == 1 && operandVals[0]->getType()->isa<builtin::FloatType>();
  }
  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    return {new builtin::BoolVarProto()};
  };
};

enum ArithOpKind {
  AddOp = 0,
  SubOp = 1,
  MulOp = 2,
  DivOp = 3,
  ModOp = 4,
};

static constexpr const char *arithOpNames[] = {"Add", "Sub", "Mul", "Div", "Mod"};

template <ArithOpKind OpKind>
class ArithOp : public OpCRTP<ArithOp<OpKind>, MathDialect, BinaryExpr> {
 public:
  ArithOp() : OpCRTP<ArithOp, MathDialect, BinaryExpr>(arithOpNames[OpKind]) {}

  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override { return checkIntMathType(operandVals); }

  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    return deduceIntType(operandProtos);
  }
};

using Add = ArithOp<AddOp>;
using Sub = ArithOp<SubOp>;
using Mul = ArithOp<MulOp>;
using Div = ArithOp<DivOp>;
using Mod = ArithOp<ModOp>;

static constexpr const char *fArithOpNames[] = {"FAdd", "FSub", "FMul", "FDiv"};

template <ArithOpKind OpKind>
class FArithOp : public OpCRTP<FArithOp<OpKind>, MathDialect, BinaryExpr> {
 public:
  FArithOp() : OpCRTP<FArithOp, MathDialect, BinaryExpr>(fArithOpNames[OpKind]) {}

  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override { return checkFloatMathType(operandVals); }

  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    return deduceIntType(operandProtos);
  }
};

using FAdd = FArithOp<AddOp>;
using FSub = FArithOp<SubOp>;
using FMul = FArithOp<MulOp>;
using FDiv = FArithOp<DivOp>;

enum CmpOpKind {
  GTOp = 0,
  GEOp = 1,
  LTOp = 2,
  LEOp = 3,
  EQOp = 4,
  UEOp = 5,
};

static constexpr const char *CmpOpNames[] = {"GT", "GE", "LT", "LE", "EQ", "UE"};

template <CmpOpKind OpKind>
class CmpOp : public OpCRTP<CmpOp<OpKind>, MathDialect, BinaryExpr> {
 public:
  CmpOp() : OpCRTP<CmpOp, MathDialect, BinaryExpr>(CmpOpNames[OpKind]) {}

  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override { return checkIntMathType(operandVals); }

  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    return deduceBoolType(operandProtos);
  }
};
using GT = CmpOp<GTOp>;
using GE = CmpOp<GEOp>;
using LT = CmpOp<LTOp>;
using LE = CmpOp<LEOp>;
using EQ = CmpOp<EQOp>;
using UE = CmpOp<UEOp>;

static constexpr const char *fCmpOpNames[] = {"FGT", "FGE", "FLT", "FLE", "FEQ", "FUE"};

template <CmpOpKind OpKind>
class FCmpOp : public OpCRTP<FCmpOp<OpKind>, MathDialect, BinaryExpr> {
 public:
  FCmpOp() : OpCRTP<FCmpOp, MathDialect, BinaryExpr>(fCmpOpNames[OpKind]) {}

  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override { return checkFloatMathType(operandVals); }

  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    return deduceBoolType(operandProtos);
  }
};
using FGT = FCmpOp<GTOp>;
using FGE = CmpOp<GEOp>;
using FLT = FCmpOp<LTOp>;
using FLE = FCmpOp<LEOp>;
using FEQ = FCmpOp<EQOp>;
using FUE = FCmpOp<UEOp>;

enum LogicOpKind {
  AndOp = 0,
  OrOp = 1,
};

// @ 逻辑操作，包含和、或，操作数为布尔值
static constexpr const char *LogicOpNames[] = {"And", "Or"};
template <LogicOpKind OpKind>
class LogicOp : public OpCRTP<LogicOp<OpKind>, MathDialect, BinaryExpr> {
 public:
  LogicOp() : OpCRTP<LogicOp, MathDialect, BinaryExpr>(LogicOpNames[OpKind]) {}

  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override { return checkBoolMathType(operandVals); }

  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    return deduceBoolType(operandProtos);
  }
};

using And = LogicOp<AndOp>;
using Or = LogicOp<OrOp>;
class IToF : public OpCRTP<IToF, MathDialect> {
 public:
  IToF() : OpCRTP<IToF, MathDialect>("IToF") {}
  bool checkType(const std::vector<lcir::Prototype *> &operandVals) override {
    if (operandVals.size() != 1) return false;
    return operandVals[0]->getType()->isa<builtin::Int32Type>();
  }
  std::vector<lcir::Prototype *> deduceRetType(const std::vector<lcir::Prototype *> &operandProtos) override {
    return {new builtin::FloatVarProto()};
  }
};

}  // namespace lcir::math